﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Collections.Generic;

namespace SGCG.Model
{

    public class Arc : BorderedGraphicObject
    {
        public Arc(GraphicObject parent) : base(parent) { }

        private int x;
        public int X
        {
            get { return x; }
            set
            {
                x = value;
                RaiseGraphicPropertyChanged("X");
            }
        }

        private int y;
        public int Y
        {
            get { return y; }
            set
            {
                y = value;
                RaiseGraphicPropertyChanged("Y");
            }
        }        
        
        private int radius;
        public int Radius
        {
            get { return radius; }
            set
            {
                radius = value;
                RaiseGraphicPropertyChanged("Radius");
            }
        }

        private double initAngle;
        public double InitAngle
        {
            get { return initAngle; }
            set
            {
                initAngle = value;
                RaiseGraphicPropertyChanged("InitAngle");
            }
        }

        private double arcAngle;
        public double ArcAngle
        {
            get { return arcAngle; }
            set
            {
                arcAngle = value;
                RaiseGraphicPropertyChanged("ArcAngle");
            }
        }

        private bool ShouldBePainted(int count, int totalCount)
        {
            int distanciaEntreTracejados = (LineStyle == LineStyle.Pontilhado) ? DotInterspace : DashInterspace;
            int tamanhoTracejado = (LineStyle == LineStyle.Pontilhado) ? 1 : DashSize;

            List<bool> toPaint = AdaptativeDashing.ComputeToPaint(totalCount, distanciaEntreTracejados, tamanhoTracejado);

            return toPaint[count];
        }

        private class AngleComparer : IComparer<IntPoint>
        {
            public AngleComparer(double angleInit, int cx, int cy)
            {
                this.angleInit = angleInit;
                this.cx = cx;
                this.cy = cy;
            }

            private double Angle(IntPoint p)
            {
                const double whole = 2 * Math.PI;
                return ((Math.Atan2(p.y - cy, p.x - cx) - angleInit) % whole + whole) % whole;
            }

            public int Compare(IntPoint a, IntPoint b)
            {
                return Angle(a).CompareTo(Angle(b));
            }

            private double angleInit;
            private int cx, cy;
        }

        public override void Draw()
        {
            List<IntPoint> points = new List<IntPoint>();
            Draw(points);
            points.Sort(new AngleComparer(ComputedInitAngle, ComputedX, ComputedY));
            if (LineStyle == LineStyle.Normal)
                foreach (IntPoint p in points)
                    SetPixel(DrawingCanvas, p.x, p.y);
            else
            {
                for (int i = 0; i < points.Count; ++i)
                    if (ShouldBePainted(i, points.Count))
                        SetPixel(DrawingCanvas, points[i].x, points[i].y);
            }
        }

        private class IntPoint
        {
            public int x, y;
        }

        private bool IsInLimits(int x, int y)
        {
            double b = ((Math.Atan2(y, x) - ComputedInitAngle) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
            return b < ArcAngle;
        }

        private int Draw(List<IntPoint> points)
        {
            Recompute();
            IDrawingCanvas drawingCanvas = this.DrawingCanvas;
            int x0 = ComputedX;
            int y0 = ComputedY;
            int count = 0;

            int radius = Radius;
            double f;
            int x, y, ddF_x, ddF_y;

            // I'm sorry. :'(
            f = ((double)5) / 4 - radius;
            x = 0;
            y = radius;
            ddF_x = 1;
            ddF_y = -2 * radius;
            while (x < y)
            {
                if (f > 0)
                {
                    --y;
                    ddF_y += 2;
                    f += ddF_y;
                }
                ++x;
                ddF_x += 2;
                f += ddF_x;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            ++x;
            while (0 < y)
            {
                if (f < 0)
                {
                    ++x;
                    ddF_x += 2;
                    f += ddF_x;
                }
                --y;
                ddF_y += 2;
                f += ddF_y;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            f = ((double)5) / 4 - radius;
            x = radius;
            y = 0;
            ddF_x = 2 * radius;
            ddF_y = 1;
            while (-x < y)
            {
                if (f > 0)
                {
                    --x;
                    ddF_x -= 2;
                    f -= ddF_x;
                }
                --y;
                ddF_y += 2;
                f += ddF_y;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            --y;
            while (0 < x)
            {
                if (f <= 0)
                {
                    --y;
                    ddF_y += 2;
                    f += ddF_y;
                }
                --x;
                ddF_x -= 2;
                f -= ddF_x;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            f = ((double)5) / 4 - radius;
            x = 0;
            y = -radius;
            ddF_x = -1;
            ddF_y = 2 * radius;
            while (y < x)
            {
                if (f > 0)
                {
                    ++y;
                    ddF_y -= 2;
                    f -= ddF_y;
                }
                --x;
                ddF_x -= 2;
                f -= ddF_x;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            --x;
            while (y < 0)
            {
                if (f <= 0)
                {
                    --x;
                    ddF_x -= 2;
                    f -= ddF_x;
                }
                ++y;
                ddF_y -= 2;
                f -= ddF_y;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            f = ((double)5) / 4 - radius;
            x = -radius;
            y = 0;
            ddF_x = -2 * radius;
            ddF_y = -1;
            while (x < -y)
            {
                if (f > 0)
                {
                    ++x;
                    ddF_x += 2;
                    f += ddF_x;
                }
                ++y;
                ddF_y -= 2;
                f -= ddF_y;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            ++y;
            while (x < 0)
            {
                if (f <= 0)
                {
                    ++y;
                    ddF_y -= 2;
                    f -= ddF_y;
                }
                ++x;
                ddF_x += 2;
                f += ddF_x;
                if (IsInLimits(x, y))
                    points.Add(new IntPoint() { x = x0 + x, y = y0 + y });
            }
            return count;
        }

        private void SetPixel(IDrawingCanvas drawingCanvas, int x, int y)
        {
            drawingCanvas.SetPixel(x, y, this.Color);

            if (this.LineThickness == Model.LineThickness.Dupla)
            {
                drawingCanvas.SetPixel(x + 1, y + 1, this.Color);
                drawingCanvas.SetPixel(x + 1, y, this.Color);
                drawingCanvas.SetPixel(x, y + 1, this.Color);
            }
        }

        protected void CopyProperties(Arc other)
        {
            base.CopyProperties(other);
            other.X = X;
            other.Y = Y;
            other.Radius = Radius;
            other.InitAngle = InitAngle;
            other.ArcAngle = ArcAngle;
        }

        public override GraphicObject CreateCopy()
        {
            Arc circle = new Arc(null);
            CopyProperties(circle);
            return circle;
        }

        public int ComputedX { get; private set; }
        public int ComputedY { get; private set; }
        public double ComputedInitAngle { get; private set; }

        private void Recompute()
        {
            Point p = TransformPoint(new Point(X, Y));
            ComputedX = (int)Math.Round(p.X);
            ComputedY = (int)Math.Round(p.Y);
            double tmp1, tmp2;
            TransformAngle(InitAngle, ArcAngle, out tmp1, out tmp2);
            ComputedInitAngle = tmp1;
        }

        public override void ConsolidateTransform(ITransform transform)
        {
            Point p = transform.Transform(new Point(X, Y));
            X = (int)Math.Round(p.X);
            Y = (int)Math.Round(p.Y);
            double tmp1, tmp2;
            transform.Transform(InitAngle, ArcAngle, out tmp1, out tmp2);
            InitAngle = tmp1;
        }

    }
}
